﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Data;
using Npgsql;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;

namespace SpectationClient.Async {

   
    public delegate void GetDataTableCompletedEventHandler(object sender, GetDataTableCompletedEventArgs e);
    

    public class GetDataTableCompletedEventArgs : AsyncCompletedEventArgs {
        private DataTable dataTable;
        public GetDataTableCompletedEventArgs(DataTable dataTable, Exception e, bool canceled, object state):base(e,canceled,state) {
            //Exception error, bool cancelled, object userState
            if(dataTable != null) this.dataTable = dataTable;
        }
        public DataTable DataTable { get { return this.dataTable; } }
    }

    /// <summary>
    /// Class to return SQL requests asynchronously. 
    /// </summary>
    public class AGetDatabaseObject : ABase {
        public event GetDataTableCompletedEventHandler GetDataTableCompleted;
        private SendOrPostCallback onGetDataTableCompletedDelegate;

    
        public AGetDatabaseObject(){
            InitializeDelegates();
        }

        new protected virtual void InitializeDelegates() {
            base.InitializeDelegates();
            onGetDataTableCompletedDelegate = new SendOrPostCallback(RequestCompleted);
        }


        // This method is invoked via the AsyncOperation object,
        // so it is guaranteed to be executed on the correct thread.
        private void RequestCompleted(object operationState) {
            GetDataTableCompletedEventArgs e = operationState as GetDataTableCompletedEventArgs;
            OnGetDataTableCompleted(e);
        }


        protected void OnGetDataTableCompleted(GetDataTableCompletedEventArgs e) {
            if(GetDataTableCompleted != null) GetDataTableCompleted(this, e);
        }

        protected override void OnCancelAsync(AsyncOperation asyncOp) {

            GetDataTableCompletedEventArgs e = new GetDataTableCompletedEventArgs(
                                    null, null, true, asyncOp.UserSuppliedState);

            // End the task. The asyncOp object is responsible 
            // for marshaling the call.
            asyncOp.PostOperationCompleted(onGetDataTableCompletedDelegate, e);
            
        }
        
        // This is the method that the underlying, free-threaded 
        // asynchronous behavior will invoke.  This will happen on
        // an arbitrary thread.
        private void CompletionMethod(DataTable dataTable, Exception exception, bool canceled, AsyncOperation asyncOp) {
            // If the task was not previously canceled,
            // remove the task from the lifetime collection.
            if(!canceled) {
                lock(userStateToLifetime.SyncRoot) {
                    userStateToLifetime.Remove(asyncOp.UserSuppliedState);
                }
            }

            // Package the results of the operation in a 
            // CalculatePrimeCompletedEventArgs.
            GetDataTableCompletedEventArgs e = new GetDataTableCompletedEventArgs(
                dataTable,
                exception,
                canceled,
                asyncOp.UserSuppliedState);

            // End the task. The asyncOp object is responsible 
            // for marshaling the call.
            asyncOp.PostOperationCompleted(onGetDataTableCompletedDelegate, e);

  
            // Note that after the call to OperationCompleted, 
            // asyncOp is no longer usable, and any attempt to use it
            // will cause an exception to be thrown.
            
        }

      
        // This method performs the actual work
        // It is executed on the worker thread.
        private void GetDataTableWorker(
            NpgsqlCommand cmd,
            AsyncOperation asyncOp) {
            Exception exception = null;

            // Check that the task is still active.
            // The operation may have been canceled before
            // the thread was scheduled.
            DataTable dt = new System.Data.DataTable();

            if(!TaskCanceled(asyncOp.UserSuppliedState)) {
                int trial = 0;
               
                //Ensure an individual connection
                cmd.Connection = cmd.Connection.Clone();
                

                bool close = cmd.Connection.State == ConnectionState.Closed;
                do {
                    cmd.Connection.Open();
                    NpgsqlTransaction trans = cmd.Connection.BeginTransaction();
                    try {
                        // Get the tableName
                        NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd);
                        da.ReturnProviderSpecificTypes = false;
                        da.Fill(dt);
                        trans.Commit();
                    } catch(Exception ex) {
                        trans.Rollback();
                        exception = ex;
                        trial++;
                    } finally {
                        cmd.Connection.ClearPool();
                        cmd.Connection.Close();
                    }
                } while(exception != null && trial < 2);
            }

            this.CompletionMethod(dt, exception, TaskCanceled(asyncOp.UserSuppliedState), asyncOp);

            //completionMethodDelegate(calcState);
        }

        // This method starts an asynchronous calculation. 
        // First, it checks the supplied task ID for uniqueness.
        // If taskId is unique, it creates a new WorkerEventHandler 
        // and calls its BeginInvoke method to start the calculation.

        public virtual void GetDataTableAsync(NpgsqlCommand cmd) {
            this.GetDataTableAsync(cmd, cmd.CommandText);
        }

        /// <summary>
        /// Starts to get a data table anynchronously.
        /// </summary>
        /// <param name="cmd">SQL command</param>
        /// <param name="taskId">ID of SQL command</param>
        public virtual void GetDataTableAsync(NpgsqlCommand cmd, object taskId) {
            // Create an AsyncOperation for taskId.
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId);

            // Multiple threads will access the task dictionary,
            // so it must be locked to serialize access.
            lock(userStateToLifetime.SyncRoot) {
                if(userStateToLifetime.Contains(taskId)) {
                    throw new ArgumentException(
                        "Task ID parameter must be unique",
                        "taskId");
                }
                userStateToLifetime[taskId] = asyncOp;
            }

            // Start the asynchronous operation.
            WorkerEventHandler workerDelegate = new WorkerEventHandler(GetDataTableWorker);
            workerDelegate.BeginInvoke(cmd, asyncOp, null, null);
        }
    }
}
